All files / web/src/app/api/vision/sessions/[id]/activate route.ts

0% Statements 0/136
0% Branches 0/1
0% Functions 0/1
0% Lines 0/136

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137                                                                                                                                                                                                                                                                                 
import { type NextRequest, NextResponse } from 'next/server'
import { eq, and } from 'drizzle-orm'
import { db } from '@/db'
import {
  visionTrainingSessions,
  type VisionTrainingSession,
} from '@/db/schema/vision-training-sessions'
import { promises as fs } from 'fs'
import path from 'path'
import { withAuth } from '@/lib/auth/withAuth'

/**
 * Model type to public directory mapping
 */
const MODEL_TYPE_TO_PUBLIC_DIR: Record<string, string> = {
  'column-classifier': 'abacus-column-classifier',
  'boundary-detector': 'abacus-boundary-detector',
}

/**
 * Copy model files to public/models/
 */
async function copyModelToPublic(
  sourcePath: string,
  modelType: 'column-classifier' | 'boundary-detector'
): Promise<void> {
  const sourceDir = path.join(process.cwd(), 'data/vision-training/models', sourcePath)
  const targetDir = path.join(process.cwd(), 'public/models', MODEL_TYPE_TO_PUBLIC_DIR[modelType])

  // Ensure target directory exists
  await fs.mkdir(targetDir, { recursive: true })

  // Read source directory
  const files = await fs.readdir(sourceDir)

  // Copy each file
  for (const file of files) {
    const sourcePath = path.join(sourceDir, file)
    const targetPath = path.join(targetDir, file)

    // Only copy regular files (not directories)
    const stat = await fs.stat(sourcePath)
    if (stat.isFile()) {
      await fs.copyFile(sourcePath, targetPath)
      console.log(`Copied ${file} to ${targetDir}`)
    }
  }
}

/**
 * Serialize a VisionTrainingSession for JSON response.
 */
function serializeSession(session: VisionTrainingSession) {
  return {
    ...session,
    createdAt: session.createdAt instanceof Date ? session.createdAt.getTime() : session.createdAt,
    trainedAt: session.trainedAt instanceof Date ? session.trainedAt.getTime() : session.trainedAt,
  }
}

/**
 * PUT /api/vision/sessions/[id]/activate
 * Set this session as the active model for its type
 *
 * This will:
 * 1. Deactivate the current active model for this type
 * 2. Copy model files to public/models/
 * 3. Mark this session as active
 */
export const PUT = withAuth(async (_request, { params }) => {
  const { id } = (await params) as { id: string }

  try {
    // Get session
    const [session] = await db
      .select()
      .from(visionTrainingSessions)
      .where(eq(visionTrainingSessions.id, id))

    if (!session) {
      return NextResponse.json({ error: 'Session not found' }, { status: 404 })
    }

    // If already active, nothing to do
    if (session.isActive) {
      return NextResponse.json({
        session: serializeSession(session),
        message: 'Session is already active',
      })
    }

    // Verify model files exist
    const sourceDir = path.join(process.cwd(), 'data/vision-training/models', session.modelPath)
    try {
      await fs.access(sourceDir)
    } catch {
      return NextResponse.json(
        {
          error: 'Model files not found',
          code: 'MODEL_FILES_MISSING',
          path: session.modelPath,
        },
        { status: 404 }
      )
    }

    // Deactivate current active model for this type
    await db
      .update(visionTrainingSessions)
      .set({ isActive: false })
      .where(
        and(
          eq(visionTrainingSessions.modelType, session.modelType),
          eq(visionTrainingSessions.isActive, true)
        )
      )

    // Copy model files to public directory
    await copyModelToPublic(session.modelPath, session.modelType)

    // Mark this session as active
    const [updatedSession] = await db
      .update(visionTrainingSessions)
      .set({ isActive: true })
      .where(eq(visionTrainingSessions.id, id))
      .returning()

    return NextResponse.json({
      session: serializeSession(updatedSession),
      message: 'Model activated successfully',
    })
  } catch (error) {
    console.error('Error activating model:', error)
    return NextResponse.json({ error: 'Failed to activate model' }, { status: 500 })
  }
})